仪表板应用教程

1. 概述

在本教程中,我们将学习如何使用 React 构建一个简单的仪表板应用。仪表板应用通常用于显示和管理各种数据,如统计信息、图表、表格等。我们将从环境搭建开始,逐步深入到组件设计、状态管理、路由配置、样式处理等方面。

2. 环境搭建

2.1 Node.js 和 npm

首先,确保你已经安装了 Node.js 和 npm。你可以通过以下命令检查它们的版本:

node -v
npm -v

如果没有安装,请访问 Node.js 官网 下载并安装。

2.2 Create React App

使用 Create React App 快速搭建 React 项目:

npx create-react-app dashboard-app
cd dashboard-app
npm start

这将创建一个基本的 React 项目,并在浏览器中打开 http://localhost:3000。

3. JSX 语法

3.1 什么是 JSX?

JSX 是 JavaScript 的语法扩展,允许你在 JavaScript 中编写类似 HTML 的代码。例如:

const element = <h1>Hello, world!</h1>;

3.2 使用 JSX

在 src/App.js 中,你可以看到类似以下的代码:

import React from 'react';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1>Welcome to the Dashboard</h1>
      </header>
    </div>
  );
}

export default App;

4. 组件基础

4.1 函数组件

函数组件是一个简单的 JavaScript 函数,返回 JSX:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

4.2 类组件

类组件使用 ES6 类来定义:

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

5. Props 和 State

5.1 Props

Props 是组件的输入参数,用于传递数据:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

function App() {
  return (
    <div>
      <Welcome name="Alice" />
      <Welcome name="Bob" />
    </div>
  );
}

5.2 State

State 是组件的内部数据,可以通过 useState Hook 或类组件的 this.state 来管理:

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

6. 生命周期方法

6.1 类组件生命周期

类组件有多个生命周期方法,如 componentDidMount、componentDidUpdate 和 componentWillUnmount:

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = { date: new Date() };
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

6.2 Hooks 生命周期

使用 useEffect Hook 可以实现类似的生命周期功能:

import React, { useState, useEffect } from 'react';

function Clock() {
  const [date, setDate] = useState(new Date());

  useEffect(() => {
    const timerID = setInterval(() => {
      setDate(new Date());
    }, 1000);

    return () => clearInterval(timerID);
  }, []);

  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {date.toLocaleTimeString()}.</h2>
    </div>
  );
}

7. 事件处理

7.1 处理点击事件

function ActionButton() {
  function handleClick() {
    alert('Button clicked!');
  }

  return (
    <button onClick={handleClick}>
      Click me
    </button>
  );
}

7.2 传递参数

function ActionButton() {
  function handleClick(message) {
    alert(message);
  }

  return (
    <button onClick={() => handleClick('Button clicked!')}>
      Click me
    </button>
  );
}

8. 条件渲染

8.1 使用 if 语句

function Greeting(props) {
  if (props.isLoggedIn) {
    return <h1>Welcome back!</h1>;
  }
  return <h1>Please sign up.</h1>;
}

8.2 使用三元运算符

function Greeting(props) {
  return (
    <h1>{props.isLoggedIn ? 'Welcome back!' : 'Please sign up.'}</h1>
  );
}

9. 列表和 Keys

9.1 渲染列表

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li key={number.toString()}>
    {number}
  </li>
);

function NumberList() {
  return (
    <ul>{listItems}</ul>
  );
}

9.2 Keys

Keys 帮助 React 识别哪些项目已更改、添加或删除。它们应该是唯一的:

const todoItems = todos.map((todo) =>
  <li key={todo.id}>
    {todo.text}
  </li>
);

10. 表单处理

10.1 受控组件

function NameForm() {
  const [value, setValue] = useState('');

  const handleChange = (event) => {
    setValue(event.target.value);
  };

  const handleSubmit = (event) => {
    alert('A name was submitted: ' + value);
    event.preventDefault();
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input type="text" value={value} onChange={handleChange} />
      </label>
      <input type="submit" value="Submit" />
    </form>
  );
}

10.2 非受控组件

function NameForm() {
  const inputRef = useRef(null);

  const handleSubmit = (event) => {
    alert('A name was submitted: ' + inputRef.current.value);
    event.preventDefault();
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input type="text" ref={inputRef} />
      </label>
      <input type="submit" value="Submit" />
    </form>
  );
}

11. 组合 vs 继承

11.1 组合

React 推荐使用组合而非继承来实现代码复用:

function Dialog(props) {
  return (
    <div className="Dialog">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
      {props.children}
    </div>
  );
}

function WelcomeDialog() {
  return (
    <Dialog
      title="Welcome"
      message="Thank you for visiting our spacecraft!" />
  );
}

11.2 继承

React 不推荐使用继承,因为组合已经足够灵活。

12. 高阶组件 (HOC)

12.1 什么是 HOC?

高阶组件是一个函数,接收一个组件并返回一个新的组件:

function withSubscription(WrappedComponent, selectData) {
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        data: selectData(DataSource, props)
      };
    }

    componentDidMount() {
      DataSource.addChangeListener(this.handleChange);
    }

    componentWillUnmount() {
      DataSource.removeChangeListener(this.handleChange);
    }

    handleChange = () => {
      this.setState({
        data: selectData(DataSource, this.props)
      });
    };

    render() {
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };
}

13. Render Props

13.1 什么是 Render Props?

Render Props 是一种在 React 组件之间共享代码的技术,通过一个函数 prop 来实现:

class MouseTracker extends React.Component {
  constructor(props) {
    super(props);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  };

  render() {
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>
    );
  }
}

function App() {
  return (
    <MouseTracker render={({ x, y }) => (
      <h1>The mouse position is ({x}, {y})</h1>
    )}/>
  );
}

14. Context API

14.1 什么是 Context?

Context 提供了一种在组件树中传递数据的方式,而不需要手动通过 props 逐层传递:

const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

15. Refs 和 DOM

15.1 使用 Refs

Refs 提供了一种访问 DOM 节点或 React 元素的方式:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }

  componentDidMount() {
    this.myRef.current.focus();
  }

  render() {
    return <input ref={this.myRef} type="text" />;
  }
}

16. useState 和 useEffect

16.1 useState

useState 是 React 的一个 Hook,用于在函数组件中添加状态:

import React, { useState } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

16.2 useEffect

useEffect 用于在函数组件中执行副作用操作:

import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

17. useContext

17.1 使用 useContext

useContext 用于在函数组件中访问 Context:

import React, { useContext } from 'react';

const ThemeContext = React.createContext('light');

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <Button theme={theme} />;
}

18. useReducer

18.1 使用 useReducer

useReducer 是 useState 的替代方案,适用于复杂的状态逻辑:

import React, { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </>
  );
}

19. 自定义 Hooks

19.1 创建自定义 Hook

自定义 Hook 是一个函数,名称以 use 开头,可以调用其他 Hook:

import { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

20. Hooks 规则和最佳实践

20.1 规则

  1. 只在顶层调用 Hooks。
  2. 只在 React 函数组件或自定义 Hook 中调用 Hooks。

20.2 最佳实践

  1. 使用 useCallback 和 useMemo 优化性能。
  2. 使用 useReducer 管理复杂状态。

21. Redux 基础

21.1 什么是 Redux?

Redux 是一个状态管理库,用于管理应用的全局状态:

import { createStore } from 'redux';

function counterReducer(state = { value: 0 }, action) {
  switch (action.type) {
    case 'counter/incremented':
      return { value: state.value + 1 };
    case 'counter/decremented':
      return { value: state.value - 1 };
    default:
      return state;
  }
}

let store = createStore(counterReducer);

store.subscribe(() => console.log(store.getState()));

store.dispatch({ type: 'counter/incremented' });
store.